import doctest

class Protein:
    MAX_STEPS = 100
    USE_CACHE = True
    
    def __init__(self, aminos):
        self.__aminos = aminos if isinstance(aminos, tuple) else tuple(aminos)
        if Protein.USE_CACHE:
            self.cache = {}
        
    def __len__(self):
        return len(self.__aminos)
    
    def __iter__(self):
        for amino in self.__aminos:
            yield amino
    
    def getamino(self, index):
        return self.__aminos[index]
    
    def run(self, context):
        '''
        >>> from eden.evo.aminos import *
        >>> from eden.evo.context import Context
        >>> from eden.evo.operands import *
        
        input/output not cached
        >>> context = Context()
        >>> amino = AddAmino(RegisterOperand(0), ConstOperand(1))
        >>> protein = Protein((amino, ))
        >>> protein.run(context)
        >>> context.getregister(0)
        1
        >>> protein.cache[(0, 0, 0, 0)]
        (1, 0, 0, 0)
        
        input/output already cached
        >>> protein.cache[(1, 0, 0, 0)] = (3, 0, 0, 0)
        >>> protein.run(context)
        >>> context.getregister(0)
        3
        '''
        if Protein.USE_CACHE:
            input = context.getregisters()
            if self.get_from_cache(context, input):
                return
        self.run_aminos(context)
        if Protein.USE_CACHE:
            self.cache_output(context, input)
    
    def get_from_cache(self, context, input):
        '''
        >>> from eden.evo.context import Context
        >>> context = Context()
        >>> protein = Protein(())
        >>> protein.cache[(1, 0, 0, 0)] = (2, 0, 0, 0)
        >>> protein.get_from_cache(context, (1, 0, 0, 0))
        True
        >>> context.getregisters()
        (2, 0, 0, 0)
        '''
        cached_output = self.cache.get(input, None)
        if cached_output is not None:
            context.setregisters(cached_output)
            return True
        else:
            return False

    def cache_output(self, context, input):
        output = context.getregisters()
        self.cache[input] = output

    def run_aminos(self, context):
        '''
        >>> from eden.evo.aminos import *
        >>> from eden.evo.context import Context
        >>> from eden.evo.operands import *
        >>> context = Context()
        >>> amino = AddAmino(RegisterOperand(0), ConstOperand(1))
        >>> protein = Protein((amino, ))
        >>> protein.run_aminos(context)
        >>> context.getregister(0)
        1
        '''
        offset = context.nextoffset()
        step = 0
        while offset < len(self.__aminos) and step < Protein.MAX_STEPS:
            self.__aminos[offset].run(context)
            offset = context.nextoffset()
            step += 1

if __name__ == '__main__':
    doctest.testmod()